#include <buola/scene/cmeshbuilder.h>
#include <buola/scene/cbone.h>

namespace buola { namespace scene {

CMeshBuilder::CMeshBuilder()
{
    mFaces.push_back(0);
}

CMeshBuilder::~CMeshBuilder()
{
}

void CMeshBuilder::SetPosition(const std::vector<mat::CVec3d> &pPosition)
{
    mPosition.mData=pPosition;
}

void CMeshBuilder::SetNormal(const std::vector<mat::CVec3d> &pNormal)
{
    mNormal.mData=pNormal;
}

void CMeshBuilder::SetTexCoord(const std::vector<mat::CVec3d> &pTexCoord)
{
    mTexCoord.mData=pTexCoord;
}

void CMeshBuilder::SetVertices(const std::vector<int> &pPosition,const std::vector<int> &pNormal,
                    const std::vector<int> &pTexCoord)
{
    mPosition.mVertices=pPosition;
    mNormal.mVertices=pNormal;
    mTexCoord.mVertices=pTexCoord;
}

void CMeshBuilder::SetFaces(const std::vector<int> &pFaces)
{
    mFaces=pFaces;
}

void CMeshBuilder::SetMaterial(const std::vector<mat::CVec4d> &pMaterials,const std::vector<int> &pIndices)
{
    mMaterial.mData=pMaterials;
    mMaterial.mVertices.resize(mPosition.mVertices.size());
    for(int i=0;i<mFaces.size()-1;i++)
    {
        for(int j=mFaces[i];j<mFaces[i+1];j++)
        {
            mMaterial.mVertices[j]=pIndices[i];
        }
    }
}

void CMeshBuilder::AddSubMesh(const std::string &pName,const std::vector<int> &pFaces,EPrimitive pPrimitiveType)
{
    SSubMesh lSubMesh;

    lSubMesh.mName=pName;
    lSubMesh.mFaces=pFaces;
    lSubMesh.mPrimitiveType=pPrimitiveType;
    
    mSubMeshes.push_back(lSubMesh);
}

void CMeshBuilder::Scale(double pScale)
{
    for(auto i=mPosition.mData.begin();i!=mPosition.mData.end();++i)
        (*i)*=pScale;
}

void CMeshBuilder::CalcNormals()
{
    for(int i=0;i<mFaces.size()-1;i++)
    {
        mat::CVec3d lA=mPosition.mData[mPosition.mVertices[mFaces[i]]];
        mat::CVec3d lB=mPosition.mData[mPosition.mVertices[mFaces[i+1]-2]];
        mat::CVec3d lC=mPosition.mData[mPosition.mVertices[mFaces[i]+1]];
        mat::CVec3d lD=mPosition.mData[mPosition.mVertices[mFaces[i+1]-1]];

        mat::CVec3d lNormal=cross(lB-lA,lD-lC);

        lNormal/=norm(lNormal);

        for(int j=mFaces[i];j<mFaces[i+1];j++)
        {
            mNormal.mVertices[j]=mNormal.mData.size();
        }

        mNormal.mData.push_back(lNormal);
    }
}

void CMeshBuilder::SmoothNormals()
{
    int lPosV[mPosition.mData.size()][10];
    int lPosVCount[mPosition.mData.size()];
    for(int i=0;i<mPosition.mData.size();i++)
        lPosVCount[i]=0;

    for(int i=0;i<mPosition.mVertices.size();i++)
    {
        int &lCount=lPosVCount[mPosition.mVertices[i]];
        if(lCount>=10) continue;
        lPosV[mPosition.mVertices[i]][lCount]=i;
        lCount++;
    }

    for(int i=0;i<mPosition.mData.size();i++)
    {
        if(lPosVCount[i]<2) continue;

        mat::CVec3d lNormal{0,0,0};
        for(int j=0;j<lPosVCount[i];j++)
        {
            lNormal+=mNormal.GetVertex(lPosV[i][j]);
        }

        lNormal/=norm(lNormal);

        for(int j=0;j<lPosVCount[i];j++)
        {
            mNormal.mVertices[lPosV[i][j]]=mNormal.mData.size();
        }

        mNormal.mData.push_back(lNormal);
    }
}

void CMeshBuilder::SmoothSkinning()
{
    int lPosV[mPosition.mData.size()][10];
    int lPosVCount[mPosition.mData.size()];
    for(int i=0;i<mPosition.mData.size();i++)
        lPosVCount[i]=0;

    for(int i=0;i<mPosition.mVertices.size();i++)
    {
        int &lCount=lPosVCount[mPosition.mVertices[i]];
        if(lCount>=10) continue;
        lPosV[mPosition.mVertices[i]][lCount]=i;
        lCount++;
    }

    for(int i=0;i<mPosition.mData.size();i++)
    {
        if(lPosVCount[i]<2) continue;

        mat::CVec3d lNormal{0,0,0};
        for(int j=1;j<lPosVCount[i];j++)
        {
            for(int k=0;k<4;k++)
            {
                mSkinIndex.mData[mSkinIndex.mVertices[lPosV[i][j]]*4+k]=mSkinIndex.mData[mSkinIndex.mVertices[lPosV[i][0]]*4+k];
                mSkinWeight.mData[mSkinWeight.mVertices[lPosV[i][j]]*4+k]=mSkinWeight.mData[mSkinWeight.mVertices[lPosV[i][0]]*4+k];
            }
        }
    }
}

void CMeshBuilder::AssignSkinIndex(const std::string &pSubMesh,int pIndex)
{
    mSkinIndex.mVertices.resize(mPosition.mVertices.size(),-1);
    mSkinWeight.mVertices.resize(mPosition.mVertices.size(),-1);
    
    int lIndex=mSkinIndex.mData.size()/4;
    mSkinIndex.mData.push_back(pIndex);
    mSkinIndex.mData.push_back(pIndex);
    mSkinIndex.mData.push_back(pIndex);
    mSkinIndex.mData.push_back(pIndex);

    int lWeight=mSkinWeight.mData.size()/4;
    mSkinWeight.mData.push_back(1.0);
    mSkinWeight.mData.push_back(0.0);
    mSkinWeight.mData.push_back(0.0);
    mSkinWeight.mData.push_back(0.0);
    
    for(SSubMesh &lSubMesh : mSubMeshes)
    {
        if(lSubMesh.mName!=pSubMesh) continue;

        for(int j=0;j<lSubMesh.mFaces.size();j++)
        {
            int lFace=lSubMesh.mFaces[j];
            for(int k=mFaces[lFace];k<mFaces[lFace+1];k++)
            {
                mSkinIndex.mVertices[k]=lIndex;
                mSkinWeight.mVertices[k]=lWeight;
            }
        }
    }
}

struct FDistanceToPoint
{
    FDistanceToPoint(const mat::CVec3d &pPoint)
        :   mPoint(pPoint)
    {}

    bool operator()(CBone *p1,CBone *p2)
    {
        return distance(p1->GetOrigin(),mPoint)<distance(p2->GetOrigin(),mPoint);
    }

    mat::CVec3d mPoint;
};

void CMeshBuilder::AssignSkinIndices(const std::string &pSubMesh,CBone *pBone)
{
    std::vector<CBone*> lChildren; //list of all relevant bones (parent and direct children)
    for(int i=0;i<pBone->GetChildren().size();i++)
        lChildren.push_back(pBone->GetChildren()[i].get());
    
    mSkinIndex.mVertices.resize(mPosition.mVertices.size(),-1);
    mSkinWeight.mVertices.resize(mPosition.mVertices.size(),-1);

    for(SSubMesh &lSubMesh : mSubMeshes)
    {
        if(lSubMesh.mName!=pSubMesh) continue;

        for(int j=0;j<lSubMesh.mFaces.size();j++)
        {
            int lFace=lSubMesh.mFaces[j];

            for(int k=mFaces[lFace];k<mFaces[lFace+1];k++)
            {
                const mat::CVec3d &lPosition=mPosition.GetVertex(k);

                //sort bones wrt distance to vertex

                int lIndices[100];
                double lWeights[100];
                int lCount=0;

                if(pBone->GetParent())
                {
                    double lWeight=pBone->CalcBlendWeight(lPosition);
                    if(lWeight==0.0)
                    {
                        lIndices[0]=pBone->GetParent()->GetIndex();;
                        lWeights[0]=1.0;
                        lCount=1;
                    }
                    else if(lWeight==1.0)
                    {
                        lIndices[0]=pBone->GetIndex();
                        lWeights[0]=1.0;
                        lCount=1;
                    }
                    else
                    {
                        lIndices[0]=pBone->GetParent()->GetIndex();
                        lWeights[0]=1.0-lWeight;
                        lIndices[1]=pBone->GetIndex();
                        lWeights[1]=lWeight;
                        lCount=2;
                    }
                }
                else
                {
                    lIndices[0]=pBone->GetIndex();
                    lWeights[0]=1.0;
                    lCount=1;
                }

                std::sort(lChildren.begin(),lChildren.end(),FDistanceToPoint(lPosition));
                
                for(int l=0;l<lChildren.size()&&lCount<4;l++)
                {
                    double lWeight=lChildren[l]->CalcBlendWeight(lPosition);
                    if(lWeight==0.0) continue;
                    if(lWeight==1.0)
                    {
                        lIndices[0]=lChildren[l]->GetIndex();
                        lWeights[0]=1.0;
                        lCount=1;
                    }
                    else
                    {
                        lIndices[lCount]=lChildren[l]->GetIndex();
                        lWeights[lCount]=lWeight;
                        for(int i=0;i<lCount;i++)
                            lWeights[i]*=1.0-lWeight;
                        lCount++;
                    }
                    break;
                }

/*
                double lAccumBlend=0.0;
                
                for(int b=0;b<lBones.size();b++)
                {
                    CBone *lBone=lBones[b];
                    //check where we are with respect to this bone

                    double lBlend=lBone->CalcBlendWeight(lPosition);

                    if(lBlend!=0.0)
                    {
                        //add this blend
                        if(lCount<3)
                        {
                            lIndices[lCount]=lBone->GetIndex();
                            lWeights[lCount]=lBlend;
                            lCount++;
                            lAccumBlend+=lBlend;
                        }
                    }
                }

                if(lAccumBlend>1.0)
                {
                    for(int l=0;l<lCount&&l<4;l++)
                        lWeights[l]/=lAccumBlend;
                }
                else if(lAccumBlend<1.0)
                {
                    lIndices[lCount]=pBone->GetIndex();//pBone->GetParent()?pBone->GetParent()->GetIndex():pBone->GetIndex();
                    lWeights[lCount]=1.0-lAccumBlend;
                    lCount++;
                }
*/
                mSkinIndex.mVertices[k]=mSkinIndex.mData.size()/4;
                mSkinWeight.mVertices[k]=mSkinWeight.mData.size()/4;

                for(int l=0;l<lCount&&l<4;l++)
                {
                    mSkinIndex.mData.push_back(lIndices[l]);
                    mSkinWeight.mData.push_back(lWeights[l]);
                }

                for(int l=lCount;l<4;l++)
                {
                    mSkinIndex.mData.push_back(0);
                    mSkinWeight.mData.push_back(0.0);
                }
            }
        }
    }
}

PMesh CMeshBuilder::BuildMesh(EPrimitive pPrimitiveType)
{
    PMesh lMesh=new CMesh;
    
    CVertexData &lVertexData=lMesh->VertexData();
    
    lVertexData.AddField<float>(FIELD_POSITION,3);
    lVertexData.AddField<float>(FIELD_NORMAL,3);
    lVertexData.AddField<float>(FIELD_TEXCOORD,2);
    lVertexData.AddField<uint32_t>(FIELD_BLEND_INDEX,4);
    lVertexData.AddField<float>(FIELD_BLEND_WEIGHTS,4);
    lVertexData.AddField<float>(FIELD_DIFFUSE,4);

    lVertexData.Resize(mPosition.mVertices.size());

    struct ATTRIBUTE_PACKED LVertex
    {
        float x,y,z;
        float xn,yn,zn;
        float u,v;
        uint32_t mIndices[4];
        float mWeights[4];
        float r,g,b,a;
    };

    LVertex *lVData=lVertexData.Map<LVertex>();

    for(int i=0;i<mPosition.mVertices.size();i++)
    {
        const mat::CVec3d &lPos=mPosition.GetVertex(i);

        lVData[i].x=lPos.x();
        lVData[i].y=lPos.y();
        lVData[i].z=lPos.z();

        const mat::CVec3d &lNormal=mNormal.GetVertex(i);

        lVData[i].xn=lNormal.x();
        lVData[i].yn=lNormal.y();
        lVData[i].zn=lNormal.z();

        if(mTexCoord.mVertices.size()>i&&mTexCoord.mVertices[i]!=-1)
        {
            const mat::CVec3d &lTexCoord=mTexCoord.GetVertex(i);
        
            lVData[i].u=lTexCoord.x();
            lVData[i].v=lTexCoord.y();
        }

        if(mSkinIndex.mVertices.size()>i)
        {
            for(int j=0;j<4;j++)
            {
                lVData[i].mIndices[j]=mSkinIndex.mData[mSkinIndex.mVertices[i]*4+j];
                lVData[i].mWeights[j]=mSkinWeight.mData[mSkinWeight.mVertices[i]*4+j];
            }
        }
        
        if(!mMaterial.IsEmpty())
        {
            const mat::CVec4d &lDiffuse=mMaterial.GetVertex(i);
            lVData[i].r=lDiffuse.x();
            lVData[i].g=lDiffuse.y();
            lVData[i].b=lDiffuse.z();
            lVData[i].a=lDiffuse.w();
        }
    }

    lVertexData.Unmap();

    int lIndexCount=0;

    for(SSubMesh &lSubMesh : mSubMeshes)
    {
        for(int j=0;j<lSubMesh.mFaces.size();j++)
        {
            int lFace=lSubMesh.mFaces[j];
            lIndexCount+=mFaces[lFace+1]-mFaces[lFace]+1;
        }
    }

    CIndexData &lIndexData=lMesh->IndexData();
    lIndexData.Resize(lIndexCount);

    GLuint *lIData=lIndexData.Map();

    int lPos=0;

    GLuint lRestartIndex=lIndexData.GetPrimitiveRestartIndex();

    for(auto &lSubMesh : mSubMeshes)
    {
        int lFirst=lPos;

        for(int j=0;j<lSubMesh.mFaces.size();j++)
        {
            int lFace=lSubMesh.mFaces[j];
            int lSize=mFaces[lFace+1]-mFaces[lFace];

            for(int k=0;k<lSize;k++)
                lIData[lPos++]=mFaces[lFace]+k;
            lIData[lPos++]=lRestartIndex;
        }

        lMesh->AddSubMesh(lSubMesh.mName,lSubMesh.mPrimitiveType,lFirst,lPos-lFirst);
    }

    lIndexData.Unmap();

    mHaveBOs=true;
    
    return lMesh;
}

/*namespace gl*/ } /*namespace buola*/ }
